package com.zc.gateway.security.filter;

import com.alibaba.fastjson.JSON;
import com.zc.commons.auth.constant.AuthCommonConstant;
import com.zc.commons.auth.dto.CurrentAuthUser;
import com.zc.commons.utils.user.AuthUserContext;
import com.zc.gateway.jwt.JsonWebJwtService;
import com.zc.gateway.security.access.RoleAccessDecisionManager;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 请求认证过滤器
 *
 * @author zcj
 * @version 1.0.0
 * @date 2022/1/7 11:06
 */
@Component
public class AuthenticationCheckFilter implements WebFilter {

    @Autowired
    private JsonWebJwtService jsonWebJwtService;
    @Autowired
    private RoleAccessDecisionManager roleAccessDecisionManager;

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        ServerHttpRequest request = serverWebExchange.getRequest();
        String path = request.getURI().getPath();
        HttpHeaders headers = request.getHeaders();
        List<String> list = headers.get(HttpHeaders.AUTHORIZATION);
        CurrentAuthUser authUser = null;
        // token解析判断凭证是否可用
        try {
            authUser = CollectionUtils.isEmpty(list) ? createAnonymousUser() : getUserByAuthorization(list.get(0));
        } catch (AuthenticationException e) {
            SecurityContextHolder.clearContext();
            return Mono.error(e);
        }
        // 写入当前线程
        AuthUserContext.setCurrent(authUser);
        // role转换 方便后续过滤器使用
        Set<GrantedAuthority> authorities = authUser.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toSet());
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authorities);
        Mono<Void> verifyMono = roleAccessDecisionManager.verify(Mono.just(authenticationToken), path);
        return verifyMono.
                doOnSuccess(aVoid -> {
                    // 重新构建在链中传递
                    ServerWebExchange exchange = serverWebExchange.mutate().principal(Mono.just(authenticationToken)).build();
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    webFilterChain.filter(exchange);
                })
                .doOnError(e -> {
                    SecurityContextHolder.clearContext();
                    Mono.error(e);
                });
    }

    /**
     * 创建匿名用户
     *
     * @return
     */
    private CurrentAuthUser createAnonymousUser() {
        CurrentAuthUser user = new CurrentAuthUser();
        user.setName("anonymousUser");
        user.beAnonymous();
        return user;

    }

    /**
     * 根据请求头获取用户
     *
     * @param authorization
     * @return
     * @throws AuthenticationException
     */
    private CurrentAuthUser getUserByAuthorization(String authorization) {
        if (authorization.startsWith(AuthCommonConstant.BEARER)) {
            String str = authorization.substring(AuthCommonConstant.BEARER.length());
            Claims body = jsonWebJwtService.getTokenBody(str);
            String userJson = (String) body.get(jsonWebJwtService.getUserClaims());
            return JSON.parseObject(userJson, CurrentAuthUser.class);
        }
        throw new BadCredentialsException("凭证信息不可用！");
    }

}
